'use strict';

const path = require('path')
const fs = require('fs')
const fse = require('fs-extra')
const semver = require('semver')

const { glob } =  require('glob')
const ejs =  require('ejs')

const Command = require('@xqc-ink-cli/command')
const Package = require('@xqc-ink-cli/package')
const log = require('@xqc-ink-cli/log')
const { spinnerStart, sleep, execCommandAsync } = require('@xqc-ink-cli/utils')

const getProjectTemplate = require('./api/getProjectTemplate')
const { template: locaTemplate} = require('./templateData')
const TYPE_PROJECT = 'project'

const TYPE_COMPONENT = 'component'
 
const TEMLATE_TYPE_NOMAL = 'normal'

const TEMLATE_TYPE_CUSTOM = 'custom'

const WHITE_COMMAND = ['npm', 'cnpm', 'yarn']

let inquirer;

class InitCommand extends Command{
    init(){
        this.projectName = this._argv[0] || ''
        this.force = !!(this._cmd.force  || this._optionValues.force)
        log.verbose('projectName', this.projectName)
        log.verbose('force', this.force) 
    }

    async exec(){
      try {
        // 0. 获取项目模板
        // const template = await getProjectTemplate()
        // if(!template || template.length === 0){
        //     throw new Error('项目模板不存在')
        // }
        this.template = locaTemplate;

        // 1. 准备阶段
        const projectInfo = await this.prepare()
        if(projectInfo){
            // 2. 下载模板
            this.projectInfo = projectInfo
            await this.downloadTemplate()
            // 3. 安装模板
            await this.installTemplate()
        }
      } catch (error) {
        log.error( error.message) 
        if(process.env.LOG_LEVEL === 'verbose'){
            console.log(error);
        }
      }
    }

    async installTemplate(){
        if(this.templateInfo) {
            if(!this.templateInfo.type) this.templateInfo.type = TEMLATE_TYPE_NOMAL
            if (this.templateInfo.type == TEMLATE_TYPE_NOMAL) {
                // 标准安装
                await this.installNormalTemplate()
            }
            else if(this.templateInfo.type == TEMLATE_TYPE_CUSTOM){
                // 自定义安装
                await this.installCustomTemplate()
            }
            else{
                throw new Error('项目模板信息类型无法识别')
            }
        }
        else{
            throw new Error('项目模板信息不存在')
        }
    }
    
    async installNormalTemplate(){
        log.verbose('templatePkgs', this.templatePkgs)
        //拷贝模板代码至当前项目
        const cliSpinner = await spinnerStart('正在安装模板...')
        try {
            const templatePath = path.resolve(this.templatePkgs.cacheFilePath, 'template')
            const targetPath = process.cwd() 
            // fse.ensureDirSync(templatePath)
            // fse.ensureDirSync(targetPath)
            fse.copySync(templatePath, targetPath)
        } catch (error) {
            throw error
        } finally {
            cliSpinner.stop(true)
            log.sucess('模板安装成功')
        }
        // egj render 忽略
        const temlateIgnore = this.templateInfo.ignore || []
        const ignore = ['**/node_modules/**', 'public/**', ...temlateIgnore];
        await this.ejsRender({ ignore : ignore })
       
        const { installCommand, startCommand } = this.templateInfo;
        await this.execCommand(installCommand, '依赖安装过程失败！')
        await this.execCommand(startCommand, '启动过程失败！') 
    }

    async execCommand(command, errMsg = ''){
        let ret;
        if(command && command.length > 0){
            const commandArr = command.split(' ')
            const cmd = WHITE_COMMAND.includes(commandArr[0]) ? commandArr[0] : null 
            if(!cmd) throw new Error('命令不存在！命令：' + cmd)
            const args = commandArr.slice(1)
            ret = await execCommandAsync(cmd, args, {
                stdio: 'inherit',
                cwd: process.cwd()
            })
        }
        if(ret !== 0) throw new Error(errMsg)
        return ret
    }
  
    /**
     * 通过glob找到package.json
     * @param {*} ignore 
     * @returns 
     */
    async ejsRender(option){
        const dir = process.cwd();
        return new Promise(async (resolve, reject) => {
           await glob('**', {
                cwd: dir,
                ignore: option.ignore || '',
                nodir: true
            })
            .catch(err => reject(err))
            .then(files => {
                Promise.all(files.map(file => {
                    const filePath = path.join(dir, file)
                    return new Promise( (resolve1, reject1) => {
                        // ejs 仅仅是拿到渲染之后的结果 并没有修改源文件并写入
                        ejs.renderFile(filePath, this.projectInfo, {},(err, result) => {
                            if(err){
                                reject1(err)
                            }
                            else {
                                fse.writeFileSync(filePath, result)
                                resolve1(result)
                            }
                        })
                    })
                }))
                .then(e => {  
                    resolve('1')
                })
                .catch(err => reject(err))
            })
        })
        
    }

    async installCustomTemplate(){
        // 查询自定义模板的入口文件
        if(await this.templatePkgs.exists()){
            const rootFile = await this.templatePkgs.getRootFilePath()
            // 以同步的方法检测目录是否存在
            if(fs.existsSync(rootFile)){
                log.notice('开始自定义模板')
                const templatePath = path.resolve(this.templatePkgs.cacheFilePath, 'template')
                const options = {
                    templateInfo: this.templateInfo,  // 模板基础信息(API接口返回)
                    projectInfo: this.projectInfo,
                    targetPath: process.cwd(), //当前项目执行路径
                    soucePath: templatePath // 当前模板缓存路径
                } 
                log.verbose('options', options)
                const code = `require('${rootFile}')(${JSON.stringify(options)})`
                log.verbose('code', code)
                await execCommandAsync('node', ['-e', code], { stdio: 'inherit', cwd: process.cwd()})
                log.sucess('自定义模板安装成功')
            }
        }
    }

    async downloadTemplate(){
        const { projectTemplate } = this.projectInfo
        const templateInfo = this.template.find(item => item.npmName === projectTemplate)
   
        const targetPath = path.resolve(process.env.CLI_HOME_PATH, 'template')
        const storeDir = path.resolve(process.env.CLI_HOME_PATH, 'template', 'node_modules')
        const { npmName, version } = templateInfo;
        
        this.templateInfo = templateInfo
        const templatePkgs = new Package({
            targetPath,
            storeDir: storeDir,
            packageName: npmName,
            packageVersion: version, 
        })
      
        if(await templatePkgs.exists()){
            const cliSpinner = await spinnerStart('正在更新模板...')
            await sleep()
            try {
                await templatePkgs.update()     
            } catch (e) {
                throw e
            } finally {
                cliSpinner.stop(true)
                if(await templatePkgs.exists()) {
                    log.sucess('模板更新成功')       
                    this.templatePkgs = templatePkgs
                }
            }
        } 
        else {   
            const cliSpinner = await spinnerStart('正在下载模板...')
            await sleep() 
            try {
                await templatePkgs.install()
            } catch (e) {
                throw e
            } finally {
                cliSpinner.stop(true)
                if(await templatePkgs.exists()) {
                    log.sucess('模板下载成功')
                    this.templatePkgs = templatePkgs
                }
            }
        } 
    }
 
    async prepare(){
        /**
         * 在 /A目录下执行，xqc-ink-cli 路径为  /Users/zhangjie/Desktop/A
         * 在 /B目录下执行，xqc-ink-cli 路径为  /Users/zhangjie/Desktop/B
         * 和 __dirname 不同
         */
        const localPath = process.cwd()
        inquirer = (await import('inquirer')).default
        // 1. 判断当前路径是否为空(是否存在资源)
        if(!this.isDirEmpty(localPath)){
            let ifContinue = false
            if(!this.force){
                log.info('当前操作不可逆')
                // 询问是否强制清空
                ifContinue = (await inquirer.prompt({ 
                    name: "ifContinue", 
                    type: 'confirm', 
                    default: false,
                    message: '当前文件夹不为空，是否继续创建项目', 
                })).ifContinue
                if(!ifContinue) return
            }
            // 强制清空(当前A或者B目录清空为空文件夹)
          
            if(ifContinue || this.force){
                const { confirmDelete } = await inquirer.prompt({
                    type: 'confirm',
                    name: "confirmDelete",
                    default: false,
                    message: '是否确认清空当前目录下的文件?',
                })
                if(confirmDelete){
                    if(localPath.includes('xqc-ink-cli'))
                    {
                        throw new Error('当前为脚手架项目路径')
                    }
                    // 清空目录
                    fse.emptyDirSync(localPath)
                }
            }
        }
        
        return this.getProjectInfo()
    }

    async getProjectInfo(){
        function isValidName(v){
            return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)
        }

        // 询问创建项目类型
        const { type } = await inquirer.prompt({
            type:'list',
            name:'type',
            message: "请选择初始化类型",
            default:TYPE_PROJECT,
            choices:[
                { name: '项目', value:TYPE_PROJECT },
                { name: '组件', value:TYPE_COMPONENT },
            ]
        })
        log.verbose('type', type)
        const typeName = type === TYPE_PROJECT ? '项目' : '组件'
         
        // ! 基于用户选择类型进行模板拆分
        this.template = this.template.filter(({ tag = []}) => tag.includes(type))

        // ! init 是否携带有效项目名称
        let projectInfo = {}
        let isProjectNameValid = false
        if(isValidName(this.projectName)){
            isProjectNameValid = true
            projectInfo.projectName = this.projectName
        }


        // 2. 获取项目的基本信息
        const projectPrompt = []
        if(!isProjectNameValid){
            projectPrompt.push({  
                type: 'input',
                name: 'projectName',
                message: `请输入${typeName}名称`,
                default: '',
                validate: function(v){
                    const done = this.async();
                    setTimeout(function() {
                        if (!isValidName(v)) {
                            done('请输入合法的项目名称');
                            return;
                        }
                        done(null, true);
                    }, 0);
                },
                filter: function(v){
                    return v
                }
            })
        }
        projectPrompt.push({
            type: 'input',
            name: 'projectVersion',
            message: `请输入${typeName}版本号`,
            default: '1.0.0',
            validate: function (v) {
                const done = this.async();
                setTimeout(function() {
                    if (!(!!semver.valid(v))) {
                        done('请输入正确版本号');
                        return;
                    }
                    done(null, true);
                }, 0);
            },
            filter: v => semver.valid(v) ? semver.valid(v) : v
        })
        projectPrompt.push({
            type:'list',
            name:'projectTemplate',
            message: `请选择${typeName}模板`,
            choices: this.createTemplateChoice()
        })
        switch (type) {
            case TYPE_PROJECT:
               {

               }
                break;
            case TYPE_COMPONENT:
                {
                    projectPrompt.push({
                        type: 'input',
                        name: 'componentDescription',
                        message: '请输入组件描述信息',
                        default: '', 
                        validate: function(v){
                            const done = this.async();
                            setTimeout(function() {
                                if (!v) {
                                    done('请输入组件描述');
                                    return;
                                }
                                done(null, true);
                            }, 0);
                        }
                    })
                }
                break;
            default:
                throw new Error('未知类型')
        }
      
        const promptAnswer = await inquirer.prompt(projectPrompt)
        projectInfo = {
            ...projectInfo,
            type,
            ...promptAnswer
        }


        // 生成classname
        if(projectInfo.projectName) {
            projectInfo.name =  projectInfo.projectName
            projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/^-/, '')
        }
        if(projectInfo.projectVersion) projectInfo.version = projectInfo.projectVersion
        if(projectInfo.componentDescription) projectInfo.description = projectInfo.componentDescription

        log.verbose('projectInfo', projectInfo)
        return projectInfo;
    }

    /**
     * localPath /Users/zhangjie/Desktop/xqc-ink-test
     * fileList []  
     */
    isDirEmpty(localPath){
        let fileList = fs.readdirSync(localPath)
        fileList = fileList.filter(file => (
            !file.startsWith('.') && ['node_modules'].indexOf(file) < 0
        ))
        return !fileList || fileList.length <= 0
    }

    createTemplateChoice(){ 
        return this.template.map(item => ({
            name: item.name,
            value: item.npmName
        }))
    }
}


function init(argv) {  
    // console.log('init', projectName, cmdObj.force, process.env.CLI_TARGET_PATH); 
    return new InitCommand(argv)
}

module.exports = init

/*
targetPath有值时 会输入lib路径，在通过require去执行
xqc-ink-cli init test -tp /Users/zhangjie/xqc-ink-cli/commands/init/lib --force --debug
在当前路径下直接执行  node -e "require('./index.js')()" 会取得相同效果
*/ 

module.exports.InitCommand = InitCommand
